{
    "cells": [
        {
            "attachments": {},
            "cell_type": "markdown",
            "metadata": {},
            "source": [
                "# lab five-axis demo\n",
                "\n",
                "this documentation gives a brief overview of 5-axis capabilities - it will be expanded in the future\n",
                "\n",
                "most of this tutorial relates to a system with B-C rotation stages, where C-axis rotations do not affect the B axis, but B-axis rotations do alter the C axis orientation (i.e. a rotating stage [C] mounted onto a tilting platform [B])\n",
                "\n",
                "the final section of this notebook demosntrates how to generate gcode for a system with a rotating nozzle (B) and rotating bed (C) \n",
                "\n",
                "the generated gcode would work on other 5-axis systems but this would likely require minor tweaks and a good understanding of the gcode requirements\n",
                "\n",
                "<*this document is a jupyter notebook - if they're new to you, check out how they work:\n",
                "[link](https://www.google.com/search?q=ipynb+tutorial),\n",
                "[link](https://jupyter.org/try-jupyter/retro/notebooks/?path=notebooks/Intro.ipynb),\n",
                "[link](https://colab.research.google.com/)*>\n",
                "\n",
                "*run all cells in this notebook in order (keep pressing shift+enter)*"
            ]
        },
        {
            "attachments": {},
            "cell_type": "markdown",
            "metadata": {},
            "source": [
                "#### five axis import"
            ]
        },
        {
            "cell_type": "code",
            "execution_count": null,
            "metadata": {},
            "outputs": [],
            "source": [
                "import lab.fullcontrol.fiveaxis as fc5\n",
                "import fullcontrol as fc\n",
                "import lab.fullcontrol as fclab"
            ]
        },
        {
            "attachments": {},
            "cell_type": "markdown",
            "metadata": {},
            "source": [
                "#### basic demo\n",
                "\n",
                "points are designed in the model's XYZ coordinate system\n",
                "\n",
                "the point x=0, y=0, z=0 in the model's coordinate system represents the intercept point of B and C axes\n",
                "\n",
                "FullControl translates them to the 3D printer XYZ coordinates, factoring in the effect of rotations to B and C axes\n",
                "\n",
                "a full explanation of this concept is out of scope for this brief tutorial notebook - google 5-axis kinematics for more info\n",
                "\n",
                "however, the following code cell briefly demonstrates how changes to the model coordinates and orientation affect the machine coordinates in interesting ways "
            ]
        },
        {
            "cell_type": "code",
            "execution_count": null,
            "metadata": {},
            "outputs": [],
            "source": [
                "steps=[]\n",
                "steps.append(fc5.Point(x=0, y=0, z=0, b=0, c=0))\n",
                "steps.append(fc5.GcodeComment(end_of_previous_line_text='start point'))\n",
                "steps.append(fc5.Point(x=1))\n",
                "steps.append(fc5.GcodeComment(end_of_previous_line_text='set x=1 - since b=0 and c=0, the model x-axis is oriented the same as the system x-axis'))\n",
                "steps.append(fc5.Point(b=45))\n",
                "steps.append(fc5.GcodeComment(end_of_previous_line_text='set b=45 - this causes a change to x and z in system coordinates'))\n",
                "steps.append(fc5.Point(b=90))\n",
                "steps.append(fc5.GcodeComment(end_of_previous_line_text='set b=90 - although x and z change, E=0 because the nozzle stays in the same point on the model'))\n",
                "steps.append(fc5.Point(b=0))\n",
                "steps.append(fc5.GcodeComment(end_of_previous_line_text='set b=0'))\n",
                "steps.append(fc5.Point(c=90))\n",
                "steps.append(fc5.GcodeComment(end_of_previous_line_text='set c=90 - this causes a change to x and y in system coordinates'))\n",
                "steps.append(fc5.Point(y=1))\n",
                "steps.append(fc5.GcodeComment(end_of_previous_line_text='set y=1 - this causes a change to x in system coordinates since the model is rotated 90 degrees'))\n",
                "print(fc5.transform(steps, 'gcode'))"
            ]
        },
        {
            "attachments": {},
            "cell_type": "markdown",
            "metadata": {},
            "source": [
                "#### add custom color to preview axes\n",
                "\n",
                "this code cell demonstrates a convenient way to add color for previews - it is not supposed to be a useful print path, it's just for demonstration\n",
                "\n",
                "after creating all the steps in the design, color is added to each Point object based on the Point's orientation in B"
            ]
        },
        {
            "cell_type": "code",
            "execution_count": null,
            "metadata": {},
            "outputs": [],
            "source": [
                "steps = []\n",
                "steps.append(fc5.Point(x=0, y=0, z=0, b=0, c=0))\n",
                "steps.append(fc5.PlotAnnotation(label='B0'))\n",
                "steps.append(fc5.Point(x=10, y=5, z=0, b=0, c=0))\n",
                "steps.append(fc5.PlotAnnotation(label='B0'))\n",
                "steps.append(fc5.Point(y=10, z=0, b=-180, c=0))\n",
                "steps.append(fc5.PlotAnnotation(label='B-180'))\n",
                "steps.append(fc5.Point(x=0, y=15, b=-180, c=0))\n",
                "steps.append(fc5.PlotAnnotation(label='B-180'))\n",
                "steps.append(fc5.Point(y=20, b=180, c=0))\n",
                "steps.append(fc5.PlotAnnotation(label='B+180'))\n",
                "steps.append(fc5.Point(x=10, y=25, b=180, c=0))\n",
                "steps.append(fc5.PlotAnnotation(label='B+180'))\n",
                "for step in steps:\n",
                "    if type(step).__name__ == 'Point':\n",
                "        # color is a gradient from B=-180 (blue) to B=+180 (red)\n",
                "        step.color = [((step.b+180)/360), 0, 1-((step.b+180)/360)]\n",
                "fc5.transform(steps, 'plot', fc5.PlotControls(color_type='manual'))"
            ]
        },
        {
            "attachments": {},
            "cell_type": "markdown",
            "metadata": {},
            "source": [
                "#### a more complex color example\n",
                "\n",
                "this example shows a wavey helical print path, where the model is continuously rotating while the nozzle gradually moves away from the print platform\n",
                "\n",
                "the part is tilted to orient the nozzle perpendicular(ish) to the wavey walls at all points"
            ]
        },
        {
            "cell_type": "code",
            "execution_count": null,
            "metadata": {},
            "outputs": [],
            "source": [
                "from math import sin, cos, tau\n",
                "steps = []\n",
                "for i in range(10001):\n",
                "    angle = tau*i/200\n",
                "    offset = (1.5*(i/10000)**2)*cos(angle*6)\n",
                "    steps.append(fc5.Point(x=(6+offset)*sin(angle), y=(6+offset)*cos(angle), z=((i/200)*0.1)-offset/2, b=(offset/1.5)*30, c=angle*360/tau))\n",
                "for step in steps:\n",
                "    if type(step).__name__ == 'Point':\n",
                "        # color is a gradient from B=0 (blue) to B=45 (red)\n",
                "        step.color = [((step.b+30)/60), 0, 1-((step.b+30)/60)]\n",
                "steps.append(fc5.PlotAnnotation(point=fc5.Point(x=0, y=0, z=8.75), label='color indicates B axis (tilt)'))\n",
                "steps.append(fc5.PlotAnnotation(point=fc5.Point(x=0, y=0, z=7.5), label='-30 deg (blue) to +30 deg (red)'))\n",
                "gcode = fc5.transform(steps,'gcode')\n",
                "print('final ten gcode lines:\\n' + '\\n'.join(gcode.split('\\n')[-10:]))\n",
                "fc5.transform(steps, 'plot', fc5.PlotControls(color_type='manual', hide_axes=False, zoom=0.75))"
            ]
        },
        {
            "attachments": {},
            "cell_type": "markdown",
            "metadata": {},
            "source": [
                "#### use 3-axis geometry functions from FullControl (with caution!)\n",
                "\n",
                "this functionality should be considered experimental at best!\n",
                "\n",
                "geometry functions that generate 3-axis points can be used - accessed via fc5.xyz_geom()\n",
                "\n",
                "but they must be translated to have 5-axis methods for gcode generation - achieved via fc5.xyz_add_bc()\n",
                "\n",
                "this conversion does not set any values of B or C attributes for those points - the BC values will remain at whatever values they were in the ***design*** before the list of converted points\n",
                "\n",
                "in the example below, a circle is created in the XY plane in the model's coordinate system, but the b-axis is set to 90\n",
                "\n",
                "therefore, the 3D printer actually prints a circle in the YZ plane since the model coordinate system has been rotated by 90 degrees about the B axis\n",
                "\n",
                "hence, when the ***design*** is transformed to a 'gcode' ***result***, Y and Z values vary in gcode while X is constant (of course this would not print well - it's just for simple demonstration)\n",
                "\n",
                "in contrast, when the ***design*** is transformed to a 'plot' ***result***, the plot shows model coordinates because 5-axis plots in the 3D-printer's coordinates system often make no sense visually"
            ]
        },
        {
            "cell_type": "code",
            "execution_count": null,
            "metadata": {},
            "outputs": [],
            "source": [
                "steps=[]\n",
                "steps.append(fc5.Point(x=10, y=0, z=0, b=90, c=0))\n",
                "xyz_geometry_steps = fc5.xyz_geom.circleXY(fc5.Point(x=0, y=0, z=0), 10, 0, 16)\n",
                "xyz_geometry_steps_with_bc_capabilities = fc5.xyz_add_bc(xyz_geometry_steps)\n",
                "steps.extend(xyz_geometry_steps_with_bc_capabilities)\n",
                "steps.append(fc5.PlotAnnotation(point=fc5.Point(x=0, y=0, z=5), label='normal FullControl geometry functions can be used via fc5.xyz_geom'))\n",
                "steps.append(fc5.PlotAnnotation(point=fc5.Point(x=0, y=0, z=3.5), label='but points must be converted to 5-axis variants via fc5.xyz_add_bc'))\n",
                "print(fc5.transform(steps, 'gcode'))\n",
                "fc5.transform(steps, 'plot')"
            ]
        },
        {
            "attachments": {},
            "cell_type": "markdown",
            "metadata": {},
            "source": [
                "#### bc_intercept\n",
                "\n",
                "if the machine's coordinate system is **not** set up so that the b and c axes intercept at the point x=0, y=0, z=0, the bc_intercept data can be provided in a GcodeControls object to ensure correct gcode generation\n",
                "\n",
                "the GcodeControls object has slightly less functionality for 5-axis FullControl compared to 3-axis FullControl - there are no printer options to choose from at present (the 'generic' printer is always used) and no built-in primer can be used\n",
                "\n",
                "note that although the system does not need the b and c axes to intercept at the point x=0, y=0, z=0, the model coordinate system must still be implemented such that the point x=0, y=0, z=0 represents the intercept point of b and c axes"
            ]
        },
        {
            "cell_type": "code",
            "execution_count": null,
            "metadata": {},
            "outputs": [],
            "source": [
                "gcode_controls = fc5.GcodeControls(bc_intercept = fc5.Point(x=10, y=0, z=0), initialization_data={'nozzle_temp': 250})\n",
                "steps=[]\n",
                "steps.append(fc5.Point(x=0, y=0, z=0, b=0, c=0))\n",
                "steps.append(fc5.GcodeComment(end_of_previous_line_text='start point (x=0 in the model but x=10 in gcode due to the bc_intercept being at x=10)'))\n",
                "steps.append(fc5.Point(x=1))\n",
                "steps.append(fc5.GcodeComment(end_of_previous_line_text='set x=1 - since b=0 and c=0, the model x-axis is oriented the same as the system x-axis'))\n",
                "steps.append(fc5.Point(b=45))\n",
                "steps.append(fc5.GcodeComment(end_of_previous_line_text='set b=45 - this causes a change to x and z in system coordinates'))\n",
                "print(fc5.transform(steps, 'gcode', gcode_controls))\n"
            ]
        },
        {
            "cell_type": "markdown",
            "metadata": {},
            "source": [
                "#### rotating-nozzle 5-axis system\n",
                "\n",
                "if the nozzle rotates about the Y axis, as opposed to the rotating bed tilting about the Y axis (as was the case for the code above), but the print bed still rotates about the Z axis, you can import `lab.fullcontrol.fiveaxisC0B1` as fc5 instead of ` lab.fullcontrol.fiveaxis`\n",
                "\n",
                "this is shown in the code cell below. note that the new import statement means the previous import of fc5 is nullified, so don't try to run the above code cells after running the next code cell or they won't work\n",
                "\n",
                "the simple instructions below show how rotation of the bed and of the nozzle independently result in necessary changes to X and Y in gcode\n",
                "\n",
                "in the future, the way of defining which type of multiaxis printer you change will be made more intuitive and procedural, but this works for now."
            ]
        },
        {
            "cell_type": "code",
            "execution_count": null,
            "metadata": {},
            "outputs": [],
            "source": [
                "import lab.fullcontrol.fiveaxisC0B1 as fc5\n",
                "b_offset_z = 40\n",
                "steps=[]\n",
                "steps.append(fc5.Point(x=0, y=0, z=0, b=0, c=0))\n",
                "steps.append(fc5.GcodeComment(end_of_previous_line_text='start point'))\n",
                "steps.extend([fc5.Point(x=1), fc5.GcodeComment(end_of_previous_line_text='x=1')])\n",
                "steps.extend([fc5.Point(c=90), fc5.GcodeComment(end_of_previous_line_text='c=90')])\n",
                "steps.extend([fc5.Point(c=180), fc5.GcodeComment(end_of_previous_line_text='c=180')])\n",
                "steps.extend([fc5.Point(c=270), fc5.GcodeComment(end_of_previous_line_text='c=270')])\n",
                "steps.extend([fc5.Point(x=0, y=1, c=0), fc5.GcodeComment(end_of_previous_line_text='x=0, y=1, c=0')])\n",
                "steps.extend([fc5.Point(c=90), fc5.GcodeComment(end_of_previous_line_text='c=90')])\n",
                "steps.extend([fc5.Point(c=180), fc5.GcodeComment(end_of_previous_line_text='c=180')])\n",
                "steps.extend([fc5.Point(c=270), fc5.GcodeComment(end_of_previous_line_text='c=270')])\n",
                "steps.extend([fc5.Point(b=90), fc5.GcodeComment(end_of_previous_line_text='b=90')])\n",
                "steps.extend([fc5.Point(b=-90), fc5.GcodeComment(end_of_previous_line_text='b=-90')])\n",
                "print(fc5.transform(steps, 'gcode', fc5.GcodeControls(b_offset_z=b_offset_z)))"
            ]
        },
        {
            "cell_type": "markdown",
            "metadata": {},
            "source": [
                "to keep the nozzle directly to the hand side of the bed (Y=0) for every point, which is useful for nozzle tilting about Y when printing a funnel for example, you need to design C to rotate for each point"
            ]
        },
        {
            "cell_type": "code",
            "execution_count": null,
            "metadata": {},
            "outputs": [],
            "source": [
                "from math import degrees\n",
                "\n",
                "circle_segments = 16\n",
                "points_per_circle = circle_segments+1\n",
                "steps = []\n",
                "xyz_geometry_steps = fc5.xyz_geom.circleXY(fc5.Point(x=0, y=0, z=0), 10, 0, circle_segments)\n",
                "xyz_geometry_steps_with_bc_capabilities = fc5.xyz_add_bc(xyz_geometry_steps)\n",
                "steps.extend(xyz_geometry_steps_with_bc_capabilities)\n",
                "steps[0].b, steps[0].c = 0.0, 0.0\n",
                "gcode_without_c_rotation = fc5.transform(steps, 'gcode', fc5.GcodeControls(b_offset_z=b_offset_z))\n",
                "fc5.transform(steps, 'plot')\n",
                "\n",
                "for i in range(len(steps)): steps[i].c = -360/circle_segments*i\n",
                "# instead of the above for loop, you can use the following function to constantly vary c automatically. This is good for more complex geometry, where c cannot be 'designed' easily.\n",
                "# steps = fclab.constant_polar_angle_with_c(points=steps, centre=fc5.Point(x=0, y=0, z=0), initial_c=-90)\n",
                "gcode_with_c_rotation = fc5.transform(steps, 'gcode', fc5.GcodeControls(b_offset_z=b_offset_z))\n",
                "\n",
                "print(gcode_without_c_rotation +\n",
                "      '\\n\\n\\ngcode with C rotation to keep nozzle directly in +X direction from bed centre:\\n\\n' + gcode_with_c_rotation)"
            ]
        },
        {
            "attachments": {},
            "cell_type": "markdown",
            "metadata": {},
            "source": [
                "#### next steps\n",
                "\n",
                "this tutorial notebook gives a brief introduction to five-axis use of FullControl for interest, but it is not an expansive implementation. it is included as an initial step towards translating in-house research for 5-axis gcode generation into a more general format compatible with the overall FullControl concept"
            ]
        }
    ],
    "metadata": {
        "kernelspec": {
            "display_name": "fc",
            "language": "python",
            "name": "python3"
        },
        "language_info": {
            "codemirror_mode": {
                "name": "ipython",
                "version": 3
            },
            "file_extension": ".py",
            "mimetype": "text/x-python",
            "name": "python",
            "nbconvert_exporter": "python",
            "pygments_lexer": "ipython3",
            "version": "3.11.3"
        },
        "vscode": {
            "interpreter": {
                "hash": "2b13a99eb0d91dd901c683fa32c6210ac0c6779bab056ce7c570b3b366dfe237"
            }
        }
    },
    "nbformat": 4,
    "nbformat_minor": 4
}
